package dev.ece.core.cells.world;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;

import dev.ece.core.cells.CellGroup;
import dev.ece.core.cells.alpha.Alpha;
import dev.ece.core.cells.decal.Decal;
import dev.ece.core.cells.events.Mouse3D;
import dev.ece.core.cells.shadow.Shadow;
import dev.ece.core.engine.EngineEvent.Pointer;
import dev.ece.core.light.Light;
import dev.ece.core.light.Lights;
import dev.ece.core.sprite.AbstractSprite;
import dev.ece.core.texture.FBO;
import dev.ece.core.texture.FBOS;
import dev.ece.core.texture.Texture;
import dev.ece.util.GLUtils;
import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.tools.Camera;
import dev.ece.util.tools.Projection;
import dev.ece.util.tools.Screen;

/**
 * @author 潜行的青衣
 * @see
 */
public class World3D extends CellGroup<AbstractSprite> implements Shadow, Alpha, Decal, Mouse3D {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7907333594777667100L;
	
	private final Logger log = LogManager.getLogger(World3D.class);
	
	private static final ThreadLocal<World3D> WORLDLOCAL = new ThreadLocal<World3D>();
	
	private Map<Integer, Pointer> buttons = new ConcurrentHashMap<Integer, Pointer>();
	
	private Camera camera = new Camera();
	
	private Screen screen;
	
	private Projection projection;
	
	//阴影部分
	private Screen shadowScreen;
	
	private Projection shadowProjection;
	
	private Camera lightCamera = new Camera();
	
	private FBO fbo;
	
	private FBO decal;
	
	private ShadowInfo shadowinfo;
	
	private boolean shadowMapping = true;
	
	private boolean mouse = true;
	
	private Light light;
	
	private final Vector2f depthPolygonOffset = new Vector2f(1.0f, 20.0f);
	
	public static final Matrix4f CONSTANTS_MATRIX = new Matrix4f().shadow();
	//阴影参数结束
	
	public World3D(Screen screen, Projection projection, 
			int shadowWidth, int shadowHeight,
			Projection shadowProjection, 
			int lightIndex) {
		if(screen == null || projection == null) {
			throw new NullPointerException();
		}
		this.screen = screen;
		this.projection = projection;
		this.camera.setPosition(0, 0, 1f);
		//阴影
		this.shadowProjection = shadowProjection;
		this.light = Lights.getLight(lightIndex);
		
		this.shadowScreen = new Screen(shadowWidth, shadowHeight);
		this.lightCamera.center.set(0, 0, 0);
		this.shadowinfo = new ShadowInfo();
		this.shadowinfo.setLightIndex(lightIndex);
	}
	
	public World3D(Screen screen, 
			int shadowWidth, int shadowHeight,
			Projection shadowProjection, 
			int lightIndex) {
		this(screen, new Projection().set3DScreen(screen), 
				shadowWidth, shadowHeight, shadowProjection, lightIndex);
	}
	
	public World3D(Screen screen) {
		this(screen, new Projection().set3DScreen(screen), screen.getWidth(), screen.getHeight(), new Projection(), 0);
		this.shadowProjection.set(-80, 80, -80, 80, 1, 1000f);
		this.shadowMapping = false;
	}
	
	@Override
	public void onInitialize() {
		try {
			fbo = FBO.newDepthInstance(this.shadowScreen.getWidth(), this.shadowScreen.getHeight());
			decal = FBO.newHDRInstance(this.getScreen().getWidth(), this.getScreen().getHeight());
			shadowinfo.setShadowMapping(fbo.getTexture());
			shadowinfo.setShadowMatrix(new Matrix4f());
		} catch (Exception e) {
			log.error("创建深度纹理FBO异常", e);
		}
	}

	public synchronized void renderDepthMap() {
		if(this.isVisible() && this.isInitialized()) {
			//深度图绘制
			shadowinfo.setEnable(this.isShadowMapping() && this.light.isEnable());
			if(shadowinfo.isEnable()) {
				WORLDLOCAL.set(this);
				this.fbo.begin();
				this.glPushScissor(this.shadowScreen.getX(), this.shadowScreen.getY(), this.shadowScreen.getWidth(), this.shadowScreen.getHeight());
				GL11.glEnable(GL11.GL_POLYGON_OFFSET_FILL);  
				GL11.glPolygonOffset(depthPolygonOffset.getX(), depthPolygonOffset.getY());
				GL11.glCullFace(GL11.GL_FRONT);
				
				shadowScreen.glViewPort();
				shadowProjection.glProjection2D();
				this.lightCamera.position.set(this.light.getPosition());
				this.lightCamera.lookAt();
				//相乘获得阴影矩阵
				shadowinfo.getShadowMatrix().mul(CONSTANTS_MATRIX, GLUtils.getProjectionViewMatrix());
				shadowinfo.store();
				
				//绘制深度图
				this.renderDepthMap(this.camera);
				GL11.glPolygonOffset(0, 0);
				GL11.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
				this.glPopScissor();
				WORLDLOCAL.remove();
			}
		}
	}

	public synchronized void logic() {
		if(this.isVisible() && this.isInitialized() && this.isLogic()) {
			WORLDLOCAL.set(this);
			this.logic(this.getCamera());
			WORLDLOCAL.remove();
		}
	}

	public synchronized void render(FBOS defer) {
		if(this.isVisible() && this.isInitialized()) {
			WORLDLOCAL.set(this);
			this.glPushScissor(this.screen.getX(), this.screen.getY(), this.screen.getWidth(), this.screen.getHeight());
			GL11.glCullFace(GL11.GL_BACK);
			this.screen.glViewPort();
			this.projection.glProjection3D();
			this.camera.lookAt();
			this.render(this.camera);
			GL11.glFlush();
			//渲染透明物体
			this.renderAlpha(defer, camera);
			//渲染贴花
			this.decal.begin();
			this.renderDeferredDecal(defer, camera);
			this.glPopScissor();
			WORLDLOCAL.remove();
		}
	}
	
	public final Vector3f[] getPointer(float winX, float winY) {
		Matrix4f project = new Matrix4f();
		project.identity();
		this.projection.glProjection3D(project);
		Matrix4f view = new Matrix4f();
		view.identity();
		this.camera.lookAt(view);
		Vector3f start = Matrix4f.gluUnProject(winX, winY, 0, project, view, screen);
		Vector3f end = Matrix4f.gluUnProject(winX, winY, 1, project, view, screen);
		return new Vector3f[] {start, end};
	}

	public synchronized boolean mouseButtonEvent(long window, int button, int action, int mods, Pointer pointer) throws Exception {
		if(this.isMouse()) {
			Vector3f[] points = getPointer(pointer.getX(), pointer.getY());
			Vector3f dir = new Vector3f();
			dir.splus(points[0], points[1]);
			Vector3f position = points[0];
			if(action == GLFW.GLFW_PRESS) {
				buttons.put(button, pointer);
				//按下
				return mouseButtonDown(button, position, dir);
			} else if(action == GLFW.GLFW_RELEASE) {
				Pointer old = buttons.get(button);
				if(old != null) {
					boolean isClick = false;
					boolean isMouseUp = mouseButtonUp(button, position, dir);
					if(old.close(pointer)) {
						isClick = mouseButtonClick(button, position, dir);
					}
					//抬起
					return isMouseUp || isClick;
				}
			}
		}
		return false;
	}

	public synchronized boolean mouseMoveEvent(long window, Pointer pointer) throws Exception {
		if(this.isMouse()) {
			Vector3f[] points = getPointer(pointer.getX(), pointer.getY());
			Vector3f dir = new Vector3f();
			dir.splus(points[0], points[1]);
			Vector3f position = points[0];
			return mouseMove(position, dir);
		}
		return false;
	}

	public synchronized boolean mouseScrollEvent(long window, double xoffset, double yoffset, Pointer pointer) throws Exception {
		if(this.isMouse()) {
			Vector3f[] points = getPointer(pointer.getX(), pointer.getY());
			Vector3f dir = new Vector3f();
			dir.splus(points[0], points[1]);
			Vector3f position = points[0];
			return mouseScroll(xoffset, yoffset, position, dir);
		}
		return false;
	}
	
	public boolean isMouse() {
		return mouse;
	}

	public void setMouse(boolean mouse) {
		this.mouse = mouse;
	}

	public Camera getCamera() {
		return camera;
	}

	public Screen getScreen() {
		return screen;
	}
	
	public Camera getLightCamera() {
		return lightCamera;
	}

	public void setScreen(Screen screen) {
		setScreen(screen, new Projection().set3DScreen(screen));
	}
	
	public void setScreen(Screen screen, Projection projection) {
		this.screen = screen;
		this.projection = projection;
	}

	public boolean isShadowMapping() {
		return shadowMapping;
	}

	public synchronized void setShadowMapping(boolean shadowMapping) {
		this.shadowMapping = shadowMapping;
	}
	
	public Texture getDepthTexture() {
		return this.fbo.getTexture();
	}
	
	public Vector2f getDepthPolygonOffset() {
		return depthPolygonOffset;
	}

	/**
	 * 获得阴影信息
	 * @return 阴影信息
	 */
	public ShadowInfo getShadowinfo() {
		return shadowinfo;
	}

	public Projection getShadowProjection() {
		return shadowProjection;
	}
	
	public Projection getProjection() {
		return projection;
	}

	public FBO getDecal() {
		return decal;
	}

	public static final World3D currentWorld3D() {
		return WORLDLOCAL.get();
	}
	
	@Override
	public void destroy() {
		super.destroy();
		if(fbo != null) {
			fbo.destroy();
			fbo = null;
		}
	}

	@Override
	public synchronized void render(Camera camera) {
		GLUtils.glPushMatrix();
		try {
			Iterator<AbstractSprite> it = this.iterator();
			AbstractSprite sprite = null;
			while(it.hasNext()) {
				sprite = it.next();
				if(sprite.isNormalRender() && sprite.isVisible() && sprite.isInitialized()) {
					sprite.render(camera);
				}
			}
		} catch(Exception e) {
			log.error("渲染普通物体异常", e);
		}
		GLUtils.glPopMatrix();
	}

	@Override
	public synchronized void renderAlpha(FBOS defer, Camera camera) {
		GLUtils.glPushMatrix();
		try {
			Iterator<AbstractSprite> it = this.iterator();
			AbstractSprite sprite = null;
			while(it.hasNext()) {
				sprite = it.next();
				if(sprite.isAlphaRender() && sprite.isVisible() && sprite.isInitialized()) {
					((Alpha) sprite).renderAlpha(defer, camera);
				}
			}
		} catch(Exception e) {
			log.error("渲染透明物体异常", e);
		}
		GLUtils.glPopMatrix();
	}

	@Override
	public synchronized void renderDeferredDecal(FBOS defer, Camera camera) {
		GLUtils.glPushMatrix();
		try {
			Iterator<AbstractSprite> it = this.iterator();
			AbstractSprite sprite = null;
			while(it.hasNext()) {
				sprite = it.next();
				if(sprite.isDecalRender() && sprite.isVisible() && sprite.isInitialized()) {
					((Decal) sprite).renderDeferredDecal(defer, camera);
				}
			}
		} catch(Exception e) {
			log.error("渲染贴花异常", e);
		}
		GLUtils.glPopMatrix();
	}

	@Override
	public synchronized void renderDepthMap(Camera camera) {
		if(this.isShadowMapping()) {
			GLUtils.glPushMatrix();
			try {
				Iterator<AbstractSprite> it = this.iterator();
				AbstractSprite sprite = null;
				while(it.hasNext()) {
					sprite = it.next();
					if(sprite.isShadowRender() && sprite.isVisible() && sprite.isInitialized()) {
						((Shadow) sprite).renderDepthMap(camera);
					}
				}
			} catch(Exception e) {
				log.error("渲染深度图异常", e);
			}
			GLUtils.glPopMatrix();
		}
	}

}
